#ifndef UL_MEMORY_POOL_H_ 
#define UL_MEMORY_POOL_H_
#include <stdint.h>
#include <pthread.h>
#include <new>
#include "ul_link.h"
#include "ul_timer.h"
#include "ul_log_adapter.h"
namespace LinuxNetworkUtility
{
  enum UlMemoryFreeFlag
  {
    /// when memory was freed, put it in the thread memory pool of the thread which frees it
    /// this type can be used to allocate memory only accessed by the thread which allocates it
    FREE_TO_THREAD_POOL = 0x00,
    /// when memory was freed, put it in the global memory pool 
    /// this type is very expensive, deprecated, can be used by global object like global cache, 
    /// or other object that manage memory by self
    FREE_TO_GLOBAL_POOL,
    /// when memory was freed, put it in the thread memory pool of the thread which allocates it
    /// this type is very high performance for producer/consumer condition, memory was allocated by 
    /// consumer and was freeed by consumer
    FREE_TO_ALLOCATED_THREAD_POOL
  };

  /// module set, used for trace memory leack
  class UlMemPoolModSet
  {
  public:
    virtual ~UlMemPoolModSet()
    {
    }   
    virtual int32_t get_mod_id(const char * mod_name)const = 0;
    virtual const char * get_mod_name(const int32_t mod_id) const = 0;
    virtual int32_t get_max_mod_num()const = 0;
    virtual void malloc(const int64_t size, const int32_t mod_id) = 0;
    virtual void free(const int64_t size, const int32_t mod_id) = 0;
    virtual void print_mod_memory_usage() = 0;
  }; 


  /// minmum block size is 1024
  static const int64_t BASE_MEMORY_BLOCK_SIZE = 1024;
  /// when a block was not used in UL_MEMORY_POOL_GC_BLOCK_AGE_IN_UL_MINUTE, it may be gc
  static const int64_t UL_MEMORY_POOL_GC_BLOCK_AGE_IN_UL_MINUTE = 1;

  enum
  {
    UL_MAX_BLOCK_SIZE_POWER_LEVEL = 24ll,   /// max block size is 2**24*1024
    /// UL_USED_BLOCK_LIST_IDX = 25ll,
    UL_REVERT_TO_THREAD_POOL_BLOCK_LIST_IDX = 25ll,
    UL_MAX_BLOCK_SIZE=1ll*(1ll<<UL_MAX_BLOCK_SIZE_POWER_LEVEL)*BASE_MEMORY_BLOCK_SIZE,
  }; 

  static const uint32_t UL_MEMORY_BLOCK_MAGIC = 0x7bb52743;

  struct UlMemoryBlock
  {
    /// @property magic number, used for validity check
    uint32_t                      magic_;
    /// @property indicate the block size including the head, the size is (2^block_size_power_)*1k bytes
    uint32_t                      block_size_power_:5;
    uint32_t                      block_in_use_:1;
    /// @property indicate how to free this block
    uint32_t                      free_indicator_flags_:2;
    uint32_t                      reserved_:3;
    /// @property last time this block was used, estimated by the process's life time,
    ///   the time unit is ul_minute, 1 ul_minute == 2^16 ms
    uint32_t                      last_access_time_:21;
    LinuxNetworkUtility::DLink    dlink_;
    /// @property last access (malloc or free) thread identifier, not pthread_t, obtained by calling gettid
    int32_t                       last_access_thead_id_;
    int32_t                       mod_id_;
    char                          buf_[0];

    inline void init(const uint32_t block_size_power, const UlMemoryFreeFlag free_flag, 
                     UlTimer & time, int32_t mod_id)
    {
      LinuxNetworkUtility::DLink *link  = NULL;
      magic_ = UL_MEMORY_BLOCK_MAGIC;
      block_size_power_=block_size_power;
      block_in_use_ = 1;
      free_indicator_flags_ = free_flag;
      reserved_ = 0;
      last_access_time_ = time.get_process_epoch_ul_minute();
      link = new (&dlink_) LinuxNetworkUtility::DLink();
      last_access_thead_id_ = gettid();
      mod_id_ = mod_id;
    } 

    inline void malloc(const UlMemoryFreeFlag free_flag, UlTimer & time, int32_t mod_id)
    {
      block_in_use_ = 1;
      free_indicator_flags_ = free_flag;
      last_access_time_ = time.get_process_epoch_ul_minute();
      dlink_.remove();
      last_access_thead_id_ = gettid();
      mod_id_ = mod_id;
    }

    inline void free(UlTimer & time,int32_t blk_size_power)
    {
      if (check_validity(blk_size_power))
      {
        block_in_use_ = 0;
        last_access_time_ = time.get_process_epoch_ul_minute();
        dlink_.remove();
        /// last_access_thead_id_ = gettid();
      }
      else
      {
        UL_LOG_ERROR("memory block validity check fail [addr:%p]", this);
      }
    }

    inline bool check_validity(int32_t blk_size_power)
    {
      bool result = true;
      if (UL_MEMORY_BLOCK_MAGIC != magic_ 
          || block_size_power_ > UL_MAX_BLOCK_SIZE_POWER_LEVEL
          || static_cast<int64_t>(block_size_power_) != blk_size_power)
      {
        result = false;
        UL_LOG_ERROR("memory block corrupt [addr:%p,block->magic:%u,expect_magic:%u,block_size_power_:%u,"
                     "max_block_size_power:%u,expect_size_power:%d]", this, magic_, 
                     UL_MEMORY_BLOCK_MAGIC, block_size_power_,  
                     UL_MAX_BLOCK_SIZE_POWER_LEVEL, blk_size_power);
      }
      return result;
    }
  };

  /// global memory pool
  class UlMemoryPool
  {
  public:
    UlMemoryPool(UlTimer & time);
    virtual ~UlMemoryPool();

    UlMemoryBlock *malloc(const int64_t size, const UlMemoryFreeFlag free_flag, int32_t mod_id);
    void free(UlMemoryBlock *block);
    /// recycle all block not accessed in the last ul minute
    void shrink();
  private:
    UlTimer &time_;
    DLink   memory_block_bucket_[UL_REVERT_TO_THREAD_POOL_BLOCK_LIST_IDX + 1];
    int64_t memory_block_num_[UL_REVERT_TO_THREAD_POOL_BLOCK_LIST_IDX + 1];
    int64_t memory_size_handled_;
    int64_t memory_size_in_use_;
    pthread_mutex_t       mutex_;
  };

  /// thread memory pool
  class UlThreadMemoryPool
  {
  public:
    UlThreadMemoryPool(UlMemoryPool &global_pool, UlMemPoolModSet &mod_set, UlTimer &time,
                       int64_t gc_frequency_us);
    virtual ~UlThreadMemoryPool();

    void *malloc(const int64_t size, const int32_t mod_id, int64_t *got_size = NULL, 
                 const UlMemoryFreeFlag flag = FREE_TO_THREAD_POOL);
    void free(void *ptr);

  private:
    void gc_();
    DLink   *cur_used_block_it_;
    DLink   memory_block_bucket_[UL_REVERT_TO_THREAD_POOL_BLOCK_LIST_IDX + 1];
    int64_t memory_block_num_[UL_REVERT_TO_THREAD_POOL_BLOCK_LIST_IDX + 1];
    int64_t memory_size_handled_;
    int64_t memory_size_in_use_;
    int64_t gc_frequency_us_;
    int64_t last_gc_time_;
    UlMemPoolModSet &mod_set_;
    UlTimer     &time_;
    UlMemoryPool    &global_pool_;
    int64_t         local_cache_hit_times_;
    int64_t         local_cache_missed_times_;
  };
}  
#endif /* UL_MEMORY_POOL_H_ */
